package club.kingyin.easycache.proxy.adapter;

import club.kingyin.easycache.cache.AbstractCache;
import club.kingyin.easycache.cache.ProxyMethodCache;
import club.kingyin.easycache.exception.InvokeException;
import club.kingyin.easycache.exception.SerializeException;
import club.kingyin.easycache.key.AbstractEasyCacheKey;
import club.kingyin.easycache.key.EasyCacheKey;
import club.kingyin.easycache.key.InvokePostProcess;
import club.kingyin.easycache.key.MethodEasyCacheKey;
import club.kingyin.easycache.method.CacheMethod;
import club.kingyin.easycache.method.EasyCacheMethod;
import club.kingyin.easycache.proxy.ProxyCacheInvokePostProcess;
import club.kingyin.easycache.proxy.ProxyKeyPostProcess;
import club.kingyin.easycache.utils.AnnotationUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.Optional;

@Slf4j
public abstract class AbstractEasyCacheMethod<T> implements EasyCacheMethod<CacheMethod>, CacheMethodKeyAdapter {

    private ProxyMethodCache cache;

    private InvokePostProcess<CacheMethod>[] postProcesses;

    private CacheMethodKeyAdapter keyAdapter;

    public void setInvokePostProcesses(InvokePostProcess<CacheMethod>[] postProcesses) {
        this.postProcesses = postProcesses;
    }

    public AbstractEasyCacheMethod(ProxyMethodCache cache, CacheMethodKeyAdapter adapter) {
        this.cache = cache;
        this.keyAdapter = adapter;
    }

    public Object odInvoke(T target) {
        return invoke(adaptation(target));
    }

    protected abstract CacheMethod adaptation(T target);

    protected void init(AbstractEasyCacheKey cacheKey) {
        AnnotationUtils.registerAnnotationHandler(cacheKey);
    }

    // 如果keyAdapter为null则调用默认的parse实现解析
    @Override
    public EasyCacheKey parse(CacheMethod sources) {
        return Optional.ofNullable(keyAdapter)
                .orElseGet(() -> target ->
                        new MethodEasyCacheKey.Builder().build(target)
                ).parse(sources);
    }

    @Override
    public Object invoke(CacheMethod target) {
        // 通过Method生成EasyCacheKey
        for (InvokePostProcess<CacheMethod> postProcess : postProcesses) {
            if (postProcess instanceof ProxyKeyPostProcess)
                postProcess.postProcessBeforeEasyCacheKey(target);
        }
        EasyCacheKey cacheKey = parse(target);
        Optional.ofNullable(cacheKey).orElseThrow(() -> new SerializeException("EasyCacheKey 不能为 null"));
        if (cacheKey instanceof AbstractEasyCacheKey) {
            log.debug("方法：[{}] EasyCacheKey [{}] 初始化", target.getMethod().getName(),cacheKey);
            init(((AbstractEasyCacheKey) cacheKey));
        } else {
            log.warn("方法：[{}] 非系统EasyCacheKey，将无法检测Key生命周期，可能造成参数失控！{}"
                    , target.getMethod().getName(),cacheKey);
        }
        // 生成key之后
        for (InvokePostProcess<CacheMethod> postProcess : postProcesses) {
            if (postProcess instanceof ProxyKeyPostProcess)
                cacheKey = postProcess.postProcessAfterEasyCacheKey(target,cacheKey);
        }

        // 打断操作
        boolean doGetCache = true;
        for (InvokePostProcess<CacheMethod> postProcess : postProcesses) {
            if (postProcess instanceof ProxyCacheInvokePostProcess)
                doGetCache = ((ProxyCacheInvokePostProcess) postProcess).getCacheInterceptor(doGetCache,target,cacheKey);
        }
        Object res;
        if (doGetCache) {
            // 分布式锁
            res = this.cache.getCache(cacheKey, target);
            log.info("方法：[{}] 缓存方法执行", target.getMethod().getName());
        } else {
            try {
                res = target.proceed();
                log.info("方法：[{}] 原始方法执行", target.getMethod().getName());
            } catch (Throwable throwable) {
                throw new InvokeException(throwable);
            }
        }
        for (InvokePostProcess<CacheMethod> postProcess : postProcesses) {
            if (postProcess instanceof ProxyCacheInvokePostProcess)
                ((ProxyCacheInvokePostProcess) postProcess).postProcessAfterGetCache(target,(AbstractCache) this.cache,res,cacheKey);
        }

        return res;
    }

}
